%% Author: GUWENQING
%% Created: 2012-4-6
%% Description: TODO: Add description to gossip_monitor
-module(gossip_monitor).

%%
%% Include files
%%

%%
%% Exported Functions
%%
-export([init/5]).

%%
%% Entry point for gossip_monitor process, passed all gossip_nodes information as well as the PID of main process.
%% Statistics information is initialized. The format is  {{ActiveSend, ActiveReceiveNormal, ActiveReceiveReject, ActiveReceiveTimeout}, {PassiveSendNormal, PassiveSendReject}}.
%% The process will go to the main loop immediately.
%%
%% Parameters:
%% 	Nodes: Array of {ID, PID} for all the gossip nodes
%% 	Main: PID of the main process
%%
init(Inits, Nodes, Main, EndType, EndCheckValue) ->
	put('EndType', EndType),
	put('EndCheckValue', EndCheckValue),
	put('Total', 0),
	NodesMap = dict:from_list(Nodes),
	StatMapList = [{ID, {{0, 0, 0, 0}, {0, 0}}} || {ID, _Pid} <- Nodes],
	StatMap = dict:from_list(StatMapList),
	GlobalMapList = [{ID, {1000, 1000, TCPU, TMEM, PLAN}} || {ID, TCPU, TMEM, PLAN} <- Inits],	
	GlobalMap = dict:from_list(GlobalMapList),
	loop(NodesMap, GlobalMap, StatMap, Main).

%%
%% Main loop for the gossip_monitor, mainly for receiving the statistics message as well as the report of value change.
%% When value change is reported, a checking of all the nodes value is done, if the values are already balanced,
%% "stop" message will be send to all the "gossip_node"s, and this monitor will also exit after a final statistics report
%% to the main process.
%%
%% Different types of received messages are handled:
%%	"report" message: update GlobalMap, and check if the data is ready
%%	"stat" message: update GlobalMap, the statistics part
%% 
%% Parameters:
%% 	Nodes: Array of {ID, PID} for all the gossip nodes
%%	GlobalMap: Array of {ID, CPUConfig, Statistics} for all the gossip nodes
%% 	Main: PID of the main process
%%
loop(NodesMap, GlobalMap, StatMap, Main) ->
	receive
		{report, NID, {{NID, TCPU, TMEM, PLAN}, {CPU, MEM}}} ->
			GlobalMap1 = dict:store(NID, {CPU, MEM, TCPU, TMEM, PLAN}, GlobalMap),
			EqualFlag = check_exit(GlobalMap1),
			if
				EqualFlag == true->
					dict:map(fun(_K, Pid)->
									 Pid ! {stop}
									 end, NodesMap),
					Main ! {finish, dict:to_list(GlobalMap1), dict:to_list(StatMap)};
				true ->
					loop(NodesMap, GlobalMap1, StatMap, Main)
			end;
		
		{stat, NID, Stat} ->
			Total = get('Total'),
			put('Total', Total+1),
			StatMap1 = update_stat(StatMap, NID, Stat),
			loop(NodesMap, GlobalMap, StatMap1, Main)
	end.

update_stat(StatMap, NID, Stat) ->
	{{I1, I2, I3, I4}, {I5, I6}} = Stat,
	{{S1, S2, S3, S4}, {S5, S6}} = dict:fetch(NID, StatMap),
	dict:store(NID, {{S1+I1, S2+I2, S3+I3, S4+I4}, {S5+I5, S6+I6}}, StatMap).

check_exit(GlobalMap) ->
	Count = dict:size(GlobalMap),
	EndType = get('EndType'),
	EndCheckValue = get('EndCheckValue'),
	GlobalMapList = dict:to_list(GlobalMap),
	PERCPUs = [TCPU / CPU || {_NID, {CPU, _MEM, TCPU, _TMEM, _PLAN}} <- GlobalMapList],
	StdDev = std_dev(PERCPUs),
	io:format("~p~n", [StdDev]),
	if
		EndType == count->
			Total = get('Total'),
			if
				Total >= EndCheckValue * Count->
					true;
				true ->
					false
			end;
		true->
			if
				(StdDev < EndCheckValue) ->
					true;
				true ->
					false
			end
	end.

std_dev(List) ->
    Average = lists:sum(List) / length(List),
    F = fun(X, Sum) -> Sum + (X - Average) * (X - Average) end,
    Variance = lists:foldl(F, 0.0, List) / length(List),
    math:sqrt(Variance).
